package block

import (
	"ChainCodeProject/wallet"
	"bytes"
	"crypto/ecdsa"
	"errors"
	"fmt"
	"ChainCodeProject/transaction"
	"github.com/boltdb/bolt"
)

/**
 * @author: 清秋
 * @email: 15770670622
 * @datetime: 13:49 2022/3/16
 * @Description:
 */

const CHAIN_DB_PATH = "./chain.db"   //数据库文件路径
const BUCKET_BLOCK = "chain_block"   //存区块桶的名字
const BUCKET_STATUS = "chain_status" //获取最后一个区块哈希值桶的名称
const LAST_HASH = "last_hash"        //第二个桶的key值

type BlockChain struct {
	DB       *bolt.DB
	LastHash []byte
	Wallet   *wallet.Wallet
}

//创建带有创世区块的区块链
func CreatChain(address string) (*BlockChain, error) {
	//打开数据库，返回一个打开好的数据库对象，和err
	db, err := bolt.Open(CHAIN_DB_PATH, 0600, nil)
	if err != nil {
		return nil,errors.New("打开数据库失败！")
	}
	var lastHash []byte
	wlt, err := wallet.NewWallet(db)
	if err != nil {
		fmt.Println("")
		return nil, err
	}
	var private *ecdsa.PrivateKey
	isFirst := false
	//想向数据库db中添加数据,更新包括增加、修改、删除
	//update同一时间内只能有一个人来写
	err = db.Update(func(tx *bolt.Tx) error {
		//1.有桶
		// tx.Bucket 在寻找桶
		bk := tx.Bucket([]byte(BUCKET_BLOCK))
		if bk == nil {
			isFirst = true
			addr, priKey, err := wlt.NewAddress()
			if err != nil {
				return err
			}
			private = priKey
			address = addr
			//传一个coinbase交易
			err, coinbase := transaction.NewCoinBase(addr)
			if err != nil {
				return err
			}
			genesis := CreatGenesis(*coinbase)

			bk, err := tx.CreateBucket([]byte(BUCKET_BLOCK))
			if err != nil {
				return errors.New("创建桶1失败！")
			}
			serialize, err := genesis.Serialize()
			if err != nil {
				return errors.New("序列化失败！")
			}
			err = bk.Put(genesis.Hash, serialize)
			if err != nil {
				return errors.New("添加数据1失败！")
			}
			//创建桶2，用来存储最新区块的hash值
			bk2, err := tx.CreateBucket([]byte(BUCKET_STATUS))
			if err != nil {
				return errors.New("创建桶2失败！")
			}
			err = bk2.Put([]byte("LAST_HASH"), genesis.Hash)
			if err != nil {
				return errors.New("添加数据2失败！")
			}
			lastHash = genesis.Hash
		} else {
			bk2 := tx.Bucket([]byte(BUCKET_STATUS))
			lastHash = bk2.Get([]byte(LAST_HASH))
		}
		return nil
	})

if isFirst{
	fmt.Println(address)
	err := wlt.SavePrivateKey(private, address)
	err = err
	if err != nil{
		fmt.Println("保存私钥错误！",err.Error())
		return nil,err
	}
}

	bc := BlockChain{
		DB:       db,
		LastHash: lastHash,
		Wallet:   wlt,
	}

	return &bc, err
}

func (bc *BlockChain) AddBlock(tx []transaction.Transaction) error {

	newBlock := CreatBlock(tx, bc.LastHash)
	err := bc.DB.Update(func(tx *bolt.Tx) error {
		bk := tx.Bucket([]byte(BUCKET_BLOCK))
		if bk == nil {
			return errors.New("没有桶1！")
		}
		serialize, err := newBlock.Serialize()
		if err != nil {
			return err
		}
		bk.Put(newBlock.Hash, serialize)

		bk2 := tx.Bucket([]byte(BUCKET_STATUS))
		if bk2 == nil {
			return errors.New("没有桶2！")
		}
		bk2.Put([]byte("LAST_HASH"), newBlock.Hash)
		bc.LastHash = newBlock.Hash
		return nil
	})
	return err
}

//创建一个迭代器的一个实例的
func (bc *BlockChain) Iterator() *ChainIterator {
	//实例化迭代器
	iterator := ChainIterator{
		DB:          bc.DB,
		currentHash: bc.LastHash,
	}
	return &iterator
}

//获取区块链中所有的区块
func (bc *BlockChain) GetAllBlock() ([]*Block, error) {
	iterator := bc.Iterator()
	blocks := []*Block{}
	for {
		if iterator.HasNext() {
			block, err := iterator.Next()
			if err != nil {
				fmt.Println(err.Error())
				return nil, err
			}
			blocks = append(blocks, block)
		} else {
			break
		}
	}
	return blocks, nil
}

//定义一个方法，用来获取某个人的所有收入
func (bc *BlockChain) FindAllOutputs(from string) ([]transaction.UTXO, error) {
	//先找所有的区块，在找所有的交易，再找所有的outputs,再找from这个人的所有收入
	blocks, err := bc.GetAllBlock()
	//声明一个结构，用来存储某个人的所有的收入
	//map结构：key-value
	//key:存储收入所有的交易hash
	//value:[]int 因为在一个交易中，可能会有多笔收入
	allOutputs := make([]transaction.UTXO, 0)
	if err != nil {
		fmt.Println("获取区块信息失败！", err.Error())
		return nil, err
	}
	//循环获取每一个区块
	for _, block := range blocks {
		//循环获取所有的交易
		for _, tx := range block.Txs {
			//循环获取所有的交易输出(收入)
			for outIndex, output := range tx.Output {
				//判断当前的这个output是不是需要的output
				if output.IsUnLock(from) {
					utxo := transaction.NewUTXO(tx.TXHash, outIndex, output)
					allOutputs = append(allOutputs, utxo)
				}
			}
		}
	}
	return allOutputs, nil
}

//用来找某个人的消费
func (bc *BlockChain) FindAllInputs(name string) []transaction.Input {
	//先找所有的区块，再找所有的交易，再找所有的inputs，再找from这个人的所有消费
	allInputs := make([]transaction.Input, 0)
	blocks, err := bc.GetAllBlock()
	if err != nil {
		fmt.Println("失败！", err.Error())
	}
	for _, block := range blocks {
		for _, tx := range block.Txs {
			for _, input := range tx.Input {
				//判断当前的消费是不是要的这个人
				if input.IsLock(name) {
					allInputs = append(allInputs, input)
				}
			}
		}
	}
	return allInputs
}

//计算本次交易余额
func (bc *BlockChain) SpendAllOutputs(outputs []transaction.UTXO, inputs []transaction.Input, amount uint) ([]transaction.UTXO, uint) {
	//所有的收入减去所有的消费
	//找到每一笔消费
	for _, input := range inputs {
		//找到每一笔收入
		for index, utxo := range outputs {
			if bytes.Compare(input.Txid, utxo.TxId) == 0 && input.Vout == utxo.Index {
				//如何从切片
				outputs = append(outputs[:index], outputs[index+1:]...)
				break
			}
		}
	}
	allOutputs := make([]transaction.UTXO, 0)
	var totalAmount uint
	for _, utxo := range outputs {
		totalAmount += utxo.Value
		allOutputs = append(allOutputs, utxo)
		if totalAmount >= amount {
			break
		}
	}
	return allOutputs, totalAmount
}

func (bc *BlockChain) NewTransaction(from, to string, amount uint) (*transaction.Transaction, error) {
	//1.构建input
	//a.在已经有点交易中，去寻找可用的的交易输出
	// 可用的交易输出(交易余额）= 所有的收入 - 所有的消费
	//所有的收入
	outputs, err := bc.FindAllOutputs(from)
	if err != nil {
		return nil, err
	}
	//所有的消费
	inputs := bc.FindAllInputs(from)
	//计算金额
	spandableOutputs, totalAmount := bc.SpendAllOutputs(outputs, inputs, amount)
	//b.从所有的余额中，取出一部分，判断是否够用，进行交易
	if spandableOutputs == nil {
		return nil, errors.New("没有可用余额！")
	}
	//累计找到够本次交易的金额
	if totalAmount < amount {
		return nil, errors.New("余额不足，无法交易！")
	}
	newTransaction, err := transaction.NewTransaction(from, to, amount, spandableOutputs)
	return newTransaction, nil
}

func (bc *BlockChain) NewCoinBase(addr string) (*transaction.Transaction, error) {
	if addr == "" || len(addr) == 0 {
		return nil, errors.New("地址错误！")
	}
	base, err := transaction.NewCoinBase(addr)
	if err != nil {
		return err, nil
	}
	return nil, base
}

func (bc *BlockChain) GetBalance(addr string) uint {
	outputs, err := bc.FindAllOutputs(addr)
	if err != nil {
		fmt.Println(err.Error())
	}
	inputs := bc.FindAllInputs(addr)
	for _, input := range inputs {
		//找到每一笔收入
		for index, utxo := range outputs {
			if bytes.Compare(input.Txid, utxo.TxId) == 0 && input.Vout == utxo.Index {
				if index >= len(outputs) {
					outputs = append(outputs[:index])
				} else {
					outputs = append(outputs[:index], outputs[index+1:]...)
				}
			}
		}
	}
	var balance uint
	for _, out := range outputs {
		balance += out.Value
	}
	return balance
}
